//
//  vhECGTools.m
//  vhECGTools
//
//  Created by jia yu on 2021/11/19.
//

#import <Foundation/Foundation.h>
#import "pthread.h"
#import "vhECGTools.h"
#import "zlib.h"
#define MPHPERKMH 1.609344f

#if TARGET_OS_MACCATALYST
#include <IOKit/pwr_mgt/IOPMLib.h>
IOPMAssertionID noSleepAssertionID;
#endif


@implementation vhECGTools
+(NSInteger)acInRegion{
    NSArray *hz60_array = @[@"US", @"AS", @"AI", @"AG", @"AW", @"BS", @"BZ", @"BM", @"BR", @"CA",@"KY", @"CO", @"CR", @"CU", @"DO", @"EC", @"SV", @"GY", @"HT", @"HN",@"JP", @"KP", @"LR", @"MX", @"FM", @"MS", @"NI", @"PA", @"PE", @"PR",@"KN", @"SA", @"SR", @"TW", @"TT", @"UM", @"VE", @"VG", @"VI"];
    NSString *current_country = [NSLocale currentLocale].countryCode;
    for (NSString *str in hz60_array){
        if ([str isEqualToString:current_country]){
            return 60;
        }
    }
    return 50;
}

+(float)filterAveValue:(float)value existValues:(NSArray <NSNumber *>* _Nullable )exists level:(NSUInteger)level{
    if (nil == exists){
        return value;
    }
    if (0 == exists.count){
        return value;
    }
    float total = value;
    int num = 1;
    for (int index=(int)(exists.count-1);index>=0;index--){
        total += [[exists objectAtIndex:index] floatValue];
        num ++;
        if (num >= level){
            break;
        }
    }
    return total/num;
}

+(float)filterHRValue:(float)value existValues:(NSArray <NSNumber *>* _Nullable )exists{
    if (nil == exists || 0 == exists.count){
        return value;
    }
    float total = 0.f;
    int num = 0;
    for (int index = (int)(exists.count-1);index>=0;index--){
        total += [[exists objectAtIndex:index] floatValue];
        num ++;
        if (num > 3){
            break;
        }
    }
    float ave = total/num;
    float rate = value/ave;
    if ( rate > 1.14 || rate < 0.8 ){
        //无效数字
        return ave;
    }
    return value;
}

+(float)printECGSecondsPaperWidth:(float)paperWidth withXRate:(float)xRate style:(ECGFormat_Style)style{
    //paperWidth 纸张宽度，单位mm
    float outputWidth = paperWidth;
    outputWidth -= 2.f;  //定标前空位
    outputWidth -= 5.f; //定标
    if (ECGFormat_Style_12x1 == style){
        //style include ST only in ECGFormat_Style_12x1
        outputWidth -= 20.f;        //ST 宽度
        outputWidth -= 4.f;         //ECG 与 ST间距
    }
    outputWidth -= 2.f; //最右侧 2mm空白
    //xRate 纸速 25mm/s 50mm/s 12.5mm/s
    return outputWidth/xRate;
}

static pthread_mutex_t create_image_color_mutex;
static BOOL create_image_color_mutex_inited = NO;
+ (UIImage *)createImageWithColor:(UIColor * _Nonnull )color size:(CGSize)size{
    if (NO == create_image_color_mutex_inited){
        int ret = pthread_mutex_init(&create_image_color_mutex, nil);
        create_image_color_mutex_inited = YES;
        if (0 != ret){
            NSLog(@"*** Warning  the create_image_color_mutex can NOT init. ***");
        }
    }
    CGSize targetSize = size;
    if (targetSize.width < 1.f || targetSize.height < 1.f){
        targetSize = CGSizeMake(1.f, 1.f);
    }
    pthread_mutex_lock(&create_image_color_mutex);
    UIGraphicsBeginImageContext(targetSize);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, CGRectMake(0.f, 0.f, targetSize.width, targetSize.height));
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    pthread_mutex_unlock(&create_image_color_mutex);
    return theImage;
}

//No Screen sleep
+(void)disableScreenSleep{
#if TARGET_OS_MACCATALYST
    noSleepAssertionID = 0;
    CFStringRef reason = (CFStringRef)@"Recording ECG for ST analisys.";
    IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, reason, &noSleepAssertionID);
    if (success == kIOReturnSuccess){
        NSLog(@"Disable screen sleep success.");
    }else{
        NSLog(@"Debug");
    }
#endif
}

+(void)enableScreenSleep{
#if TARGET_OS_MACCATALYST
    if (0 != noSleepAssertionID){
        IOPMAssertionRelease(noSleepAssertionID);
        noSleepAssertionID = 0;
    }
#endif
}

+(void)alertWith:(NSString *)message{
    if (NO == [NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [vhECGTools alertWith:message];
        });
        return;
    }
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"vhStressECG" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
    [alert addAction:action];
    [[vhECGTools currentRootVC] presentViewController:alert animated:YES completion:nil];
    return;
}

+(NSInteger)ageWithBirthday:(NSDate * _Nonnull )birthday at:(NSDate * _Nullable )atDate{
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:kCFCalendarUnitYear|kCFCalendarUnitMonth|kCFCalendarUnitDay fromDate:birthday];
    NSInteger year = [components year];
    NSInteger month = [components month];
    NSInteger day = [components day];

    if (nil == atDate){
        atDate = [NSDate date];
    }
    components = [cal components:kCFCalendarUnitYear|kCFCalendarUnitMonth|kCFCalendarUnitDay fromDate:atDate];
    NSInteger toYear = [components year];
    NSInteger toMonth = [components month];
    NSInteger toDay = [components day];

    NSInteger age = toYear - year;
    if (toMonth < month){
        age --;
    }else if (toMonth == month){
        if (toDay < day){
            age --;
        }
    }
    return age;
}

+(CGPoint)middlePointA:(CGPoint)a pointB:(CGPoint)b{
    return CGPointMake((a.x + b.x)/2.f, (a.y+b.y)/2.f);
}

static pthread_mutex_t scale_image_mutex;
static BOOL scale_image_mutex_inited = NO;
+(UIImage *)scaleImage:(UIImage *)fromImage to:(CGSize)targetSize{
    if (NO == scale_image_mutex_inited){
        int ret = pthread_mutex_init(&scale_image_mutex, nil);
        scale_image_mutex_inited = YES;
        if (0 != ret){
            NSLog(@"*** Warning  the scale_image_mutex can NOT init. ***");
        }
    }
    pthread_mutex_lock(&scale_image_mutex);
    UIGraphicsBeginImageContextWithOptions(targetSize, 0, UIScreen.mainScreen.scale);
    [fromImage drawInRect:CGRectMake(0,0, targetSize.width, targetSize.height)];
    UIImage *res  = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    pthread_mutex_unlock(&scale_image_mutex);
    return res;
}

#pragma mark - About Views and Colors
+(UIViewController *)getCurrentVCFrom:(UIViewController *)fromViewController{
    UIViewController *currentVC;
    if ([fromViewController presentedViewController]){
        fromViewController = [fromViewController presentedViewController];
    }
    if ([fromViewController isKindOfClass:[UITabBarController class]]){
        currentVC = [self getCurrentVCFrom:[(UITabBarController *)fromViewController selectedViewController]];
    }else if ([fromViewController isKindOfClass:[UINavigationController class]]){
        currentVC = [self getCurrentVCFrom:[(UINavigationController *)fromViewController visibleViewController]];
    }else{
        currentVC = fromViewController;
    }
    return currentVC;
}

+(UIViewController *)currentRootVC NS_EXTENSION_UNAVAILABLE_IOS(""){
    UIViewController *from = [UIApplication.sharedApplication windows].firstObject.rootViewController;
    //UIApplication.sharedApplication.keyWindow.rootViewController
    return [self getCurrentVCFrom:from];
}

+(float)getTargetValueWith:(float)value fromMin:(float)fromMin fromMax:(float)fromMax toMin:(float)toMin toMax:(float)toMax{
    float percent = (value - fromMin)/(fromMax - fromMin);
    return toMin + percent * (toMax - toMin);
}

+(float)waveLengthWithST:(float)st{
    if (st >= 0.3f){
        return 640.f;
    }else if (st <= -0.3f){
        return 440.f;
    }else if (st <= -0.1f){
        //440 - 470
        return [vhECGTools getTargetValueWith:st fromMin:-0.3f fromMax:-0.1f toMin:440.f toMax:470.f];
    }else if (st <= -0.05f){
        // 470 - 490
        return [vhECGTools getTargetValueWith:st fromMin:-0.1f fromMax:-0.05f toMin:470.f toMax:490.f];
    }else if (st < 0.f){
        //490 - 530
        return [vhECGTools getTargetValueWith:st fromMin:-0.05f fromMax:0.f toMin:490 toMax:530.f];
    }else if (st < 0.05f){
        //530 - 580
        return [vhECGTools getTargetValueWith:st fromMin:0.f fromMax:0.05f toMin:530 toMax:580.f];
    }else if (st < 0.1f){
        //580 - 599
        return [vhECGTools getTargetValueWith:st fromMin:0.05f fromMax:0.1f toMin:580 toMax:599.f];
    }else if (st < 0.3f){
        //599 - 640
        return [vhECGTools getTargetValueWith:st fromMin:0.1f fromMax:0.3f toMin:599 toMax:640.f];
    }
    return 530.f;
}

+(UIColor *)colorWithST:(float)st{
    float wavelength = [vhECGTools waveLengthWithST:st];
    return [vhECGTools colorWithLight:wavelength];
}

+(UIColor *)colorWithLight:(float)lambda{
    double r, g, b, alpha;
        if (lambda >= 380.0 && lambda < 440.0) {
            r = -1.0 * (lambda - 440.0) / (440.0 - 380.0);
            g = 0.0;
            b = 1.0;
        }else if (lambda >= 440.0 && lambda < 490.0) {
            r = 0.0;
            g = (lambda - 440.0) / (490.0 - 440.0);
            b = 1.0;
        }else if (lambda >= 490.0 && lambda < 510.0) {
            r = 0.0;
            g = 1.0;
            b = -1.0 * (lambda - 510.0) / (510.0 - 490.0);
        }else if (lambda >= 510.0 && lambda < 580.0) {
            r = (lambda - 510.0) / (580.0 - 510.0);
            g = 1.0;
            b = 0.0;
        }else if (lambda >= 580.0 && lambda < 645.0) {
            r = 1.0;
            g = -1.0 * (lambda - 645.0) / (645.0 - 580.0);
            b = 0.0;
        }else if (lambda >= 645.0 && lambda <= 780.0) {
            r = 1.0;
            g = 0.0;
            b = 0.0;
        }else {
            r = 0.0;
            g = 0.0;
            b = 0.0;
        }

        //在可见光谱的边缘处强度较低。
        if (lambda >= 380.0 && lambda < 420.0) {
            alpha = 0.30 + 0.70 * (lambda - 380.0) / (420.0 - 380.0);
        }else if (lambda >= 420.0 && lambda < 701.0) {
            alpha = 1.0;
        }else if (lambda >= 701.0 && lambda < 780.0) {
            alpha = 0.30 + 0.70 * (780.0 - lambda) / (780.0 - 700.0);
        }else {
            alpha = 0.0;
        }

        //1953年在引入NTSC电视时,计算具有荧光体的监视器的亮度公式如下
//        int Y = static_cast<int>(0.212671*r + 0.715160*g + 0.072169*b);

        //伽马射线 gamma
        //照明强度 intensityMax
//        int R = r == 0.0 ? 0 : static_cast<int>(std::round(intensityMax * std::pow(r * alpha, gamma)));
//        int G = g == 0.0 ? 0 : static_cast<int>(std::round(intensityMax * std::pow(g * alpha, gamma)));
//        int B = b == 0.0 ? 0 : static_cast<int>(std::round(intensityMax * std::pow(b * alpha, gamma)));
//        int A = static_cast<int>(alpha);
//
//        return std::vector<int>{R, G, B, A, Y};
    return [UIColor colorWithRed:r green:g blue:b alpha:1.f];
}


+(NSInteger)oneStringInteger:(NSString *)str{
    if ([str integerValue] > 0){
        return [str integerValue];
    }
    if ([str isEqualToString:@"0"]){
        return 0;
    }
    NSString *upp = [str uppercaseString];
    if ([upp isEqualToString:@"A"]){
        return 10;
    }else if ([upp isEqualToString:@"B"]){
        return 11;
    }else if ([upp isEqualToString:@"C"]){
        return 12;
    }else if ([upp isEqualToString:@"D"]){
        return 13;
    }else if ([upp isEqualToString:@"E"]){
        return 14;
    }else if ([upp isEqualToString:@"F"]){
        return 15;
    }
    return 0;
}
+(NSInteger)integerFromString:(NSString *)str{
    if (nil == str){
        return 0;
    }
    if (2 != str.length){
        return 0;
    }
    NSString *h = [str substringToIndex:1];
    NSString *l = [str substringFromIndex:1];
    return [self oneStringInteger:h] * 16 + [self oneStringInteger:l];
}

+(UIColor *)colorFromHEXStringwithR:(NSString *)r G:(NSString *)g B:(NSString *)b{
    return [UIColor colorWithRed:[self integerFromString:r]/255.f green:[self integerFromString:g]/255.f blue:[self integerFromString:b]/255.f alpha:1.f];
}
+(UIColor *)colorFromHEXStringwithR:(NSString *)r G:(NSString *)g B:(NSString *)b A:(float)alpha{
    return [UIColor colorWithRed:[self integerFromString:r]/255.f green:[self integerFromString:g]/255.f blue:[self integerFromString:b]/255.f alpha:alpha];
}
+(UIColor *)colorFromHEXString:(NSString *)rgb{
    NSString *r = [rgb substringWithRange:NSMakeRange(0, 2)];
    NSString *g = [rgb substringWithRange:NSMakeRange(2, 2)];
    NSString *b = [rgb substringWithRange:NSMakeRange(4, 2)];
    return [self colorFromHEXStringwithR:r G:g B:b];
}
+(UIColor *)colorFromHEXString:(NSString *)rgb withAlpha:(float)alpha{
    NSString *r = [rgb substringWithRange:NSMakeRange(0, 2)];
    NSString *g = [rgb substringWithRange:NSMakeRange(2, 2)];
    NSString *b = [rgb substringWithRange:NSMakeRange(4, 2)];
    return [self colorFromHEXStringwithR:r G:g B:b A:alpha];
}

#pragma mark - About ECG
+(NSString *)leadName:(ECGLead)lead{
    switch (lead) {
        case ECGLead_All:
            return @"All";
        case ECGLead_I:
            return @"I";
        case ECGLead_II:
            return @"II";
        case ECGLead_III:
            return @"III";
        case ECGLead_aVR:
            return @"aVR";
        case ECGLead_aVL:
            return @"aVL";
        case ECGLead_aVF:
            return @"aVF";
        case ECGLead_V1:
            return @"V1";
        case ECGLead_V2:
            return @"V2";
        case ECGLead_V3:
            return @"V3";
        case ECGLead_V4:
            return @"V4";
        case ECGLead_V5:
            return @"V5";
        case ECGLead_V6:
            return @"V6";

        default:
            break;
    }
    return @"";
}

#define MAXIMUM_COMPRESS_ECG_LEN 660 * 8 * 60 * 10
+(NSData *)ecg_compress:(NSArray <NSArray <NSNumber *>*>*)ecg_array_array{
    short value[MAXIMUM_COMPRESS_ECG_LEN];
    int valueIndex = 0;
    int keepNum = (int)ecg_array_array.firstObject.count;
    if (keepNum > MAXIMUM_COMPRESS_ECG_LEN){
        NSLog(@"Failed: your ECG is to large.");
        return nil;
    }
    for (int i=0;i<keepNum;i++){
        value[valueIndex] = [[[ecg_array_array objectAtIndex:0] objectAtIndex:i] shortValue];
        value[valueIndex+1] = [[[ecg_array_array objectAtIndex:1] objectAtIndex:i] shortValue];
        value[valueIndex+2] = [[[ecg_array_array objectAtIndex:6] objectAtIndex:i] shortValue];
        value[valueIndex+3] = [[[ecg_array_array objectAtIndex:7] objectAtIndex:i] shortValue];
        value[valueIndex+4] = [[[ecg_array_array objectAtIndex:8] objectAtIndex:i] shortValue];
        value[valueIndex+5] = [[[ecg_array_array objectAtIndex:9] objectAtIndex:i] shortValue];
        value[valueIndex+6] = [[[ecg_array_array objectAtIndex:10] objectAtIndex:i] shortValue];
        value[valueIndex+7] = [[[ecg_array_array objectAtIndex:11] objectAtIndex:i] shortValue];
        valueIndex += 8;
    }
    NSData *pUncompressedData = [[NSData alloc] initWithBytes:(void *)value length:keepNum*8*2];
    if (!pUncompressedData || [pUncompressedData length] == 0)
    {
        NSLog(@"%s: Error: Can't compress an empty or null NSData object.", __func__);
        return nil;
    }

    z_stream zlibStreamStruct;
    zlibStreamStruct.zalloc    = Z_NULL; // Set zalloc, zfree, and opaque to Z_NULL so
    zlibStreamStruct.zfree     = Z_NULL; // that when we call deflateInit2 they will be
    zlibStreamStruct.opaque    = Z_NULL; // updated to use default allocation functions.
    zlibStreamStruct.total_out = 0;      // Total number of output bytes produced so far
    zlibStreamStruct.next_in   = (Bytef*)[pUncompressedData bytes]; // Pointer to input bytes
    zlibStreamStruct.avail_in  = (unsigned int)[pUncompressedData length]; // Number of input bytes left to process

    int initError = deflateInit2(&zlibStreamStruct, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
    if (initError != Z_OK)
    {
      NSString *errorMsg = nil;
      switch (initError)
      {
          case Z_STREAM_ERROR:
             errorMsg = @"Invalid parameter passed in to function.";
             break;
          case Z_MEM_ERROR:
             errorMsg = @"Insufficient memory.";
             break;
          case Z_VERSION_ERROR:
              errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
              break;
          default:
              errorMsg = @"Unknown error code.";
             break;
      }
      NSLog(@"%s: deflateInit2() Error: \"%@\" Message: \"%s\"", __func__, errorMsg, zlibStreamStruct.msg);
      return nil;
    }

    // Create output memory buffer for compressed data. The zlib documentation states that
    // destination buffer size must be at least 0.1% larger than avail_in plus 12 bytes.
    NSMutableData *compressedData = [NSMutableData dataWithLength:[pUncompressedData length] * 1.01 + 12];

    int deflateStatus;
    do
    {
      // Store location where next byte should be put in next_out
      zlibStreamStruct.next_out = [compressedData mutableBytes] + zlibStreamStruct.total_out;

      // Calculate the amount of remaining free space in the output buffer
      // by subtracting the number of bytes that have been written so far
     // from the buffer's total capacity
      zlibStreamStruct.avail_out = (unsigned int)[compressedData length] - (unsigned int)zlibStreamStruct.total_out;
      deflateStatus = deflate(&zlibStreamStruct, Z_FINISH);

    } while ( deflateStatus == Z_OK );

    // Check for zlib error and convert code to usable error message if appropriate
    if (deflateStatus != Z_STREAM_END)
    {
      NSString *errorMsg = nil;
      switch (deflateStatus)
      {
          case Z_ERRNO:
             errorMsg = @"Error occured while reading file.";
             break;
          case Z_STREAM_ERROR:
              errorMsg = @"The stream state was inconsistent (e.g., next_in or next_out was NULL).";
              break;
          case Z_DATA_ERROR:
              errorMsg = @"The deflate data was invalid or incomplete.";
              break;
          case Z_MEM_ERROR:
              errorMsg = @"Memory could not be allocated for processing.";
              break;
          case Z_BUF_ERROR:
              errorMsg = @"Ran out of output buffer for writing compressed bytes.";
             break;
          case Z_VERSION_ERROR:
              errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
              break;
          default:
              errorMsg = @"Unknown error code.";
              break;
      }
      NSLog(@"%s: zlib error while attempting compression: \"%@\" Message: \"%s\"", __func__, errorMsg, zlibStreamStruct.msg);

      // Free data structures that were dynamically created for the stream.
      deflateEnd(&zlibStreamStruct);

      return nil;
    }
    // Free data structures that were dynamically created for the stream.
    deflateEnd(&zlibStreamStruct);
    [compressedData setLength: zlibStreamStruct.total_out];
    NSLog(@"%s: Compressed file from %d KB to %d KB", __func__, (int)[pUncompressedData length]/1024, (int)[compressedData length]/1024);
    return compressedData;
}

+(NSArray <NSArray <NSNumber *>*>* _Nullable )ecg_uncompress:(NSData * _Nonnull)compressedData{
    if (nil == compressedData || 0 == compressedData.length){
        return nil;
    }
    unsigned full_length = (unsigned int)[compressedData length];
    unsigned half_length = (unsigned int)[compressedData length]/2;
    
    NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
    BOOL done = NO;
    int status;
    
    z_stream strm;
    strm.next_in = (Bytef *)[compressedData bytes];
    strm.avail_in = (unsigned int)[compressedData length];
    strm.total_out = 0;
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    if (inflateInit2(&strm, (15+32)) != Z_OK)
        return nil;
    
    while (!done) {
        // Make sure we have enough room and reset the lengths.
        if (strm.total_out >= [decompressed length]) {
            [decompressed increaseLengthBy: half_length];
        }
        strm.next_out = [decompressed mutableBytes] + strm.total_out;
        strm.avail_out = (unsigned int)[decompressed length] - (unsigned int)strm.total_out;
        // Inflate another chunk.
        status = inflate (&strm, Z_SYNC_FLUSH);
        if (status == Z_STREAM_END) {
            done = YES;
        } else if (status != Z_OK) {
            break;
        }
    }
    
    if (inflateEnd (&strm) != Z_OK)
        return nil;
    
    // Set real length.
    const void *short_data;
    if (done) {
        [decompressed setLength: strm.total_out];
        short_data = [decompressed bytes];
    }else{
        return nil;
    }

    
    NSMutableArray *res = [[NSMutableArray alloc] init];
    for (int i=0;i<12;i++){
        NSMutableArray *lead = [[NSMutableArray alloc] init];
        [res addObject:lead];
    }
    char *p = (char *)short_data;
    for (int i=0;i<strm.total_out;i+=16){
        short *I = (short *)p;
        short *II = (short *)(p+2);
        // III = II - I
        // aVR = -(I+II)/2
        // aVL = I -II/2
        // aVF = II-I/2
        short III = *II - *I;
        short aVR = - (*I + *II)/2;
        short aVL = *I - *II/2;
        short aVF = *II - *I/2;
        short *V1 = (short *)(p+4);
        short *V2 = (short *)(p+6);
        short *V3 = (short *)(p+8);
        short *V4 = (short *)(p+10);
        short *V5 = (short *)(p+12);
        short *V6 = (short *)(p+14);
        [[res objectAtIndex:0] addObject:[NSNumber numberWithShort:*I]];
        [[res objectAtIndex:1] addObject:[NSNumber numberWithShort:*II]];
        [[res objectAtIndex:2] addObject:[NSNumber numberWithShort:III]];
        [[res objectAtIndex:3] addObject:[NSNumber numberWithShort:aVR]];
        [[res objectAtIndex:4] addObject:[NSNumber numberWithShort:aVL]];
        [[res objectAtIndex:5] addObject:[NSNumber numberWithShort:aVF]];
        [[res objectAtIndex:6] addObject:[NSNumber numberWithShort:*V1]];
        [[res objectAtIndex:7] addObject:[NSNumber numberWithShort:*V2]];
        [[res objectAtIndex:8] addObject:[NSNumber numberWithShort:*V3]];
        [[res objectAtIndex:9] addObject:[NSNumber numberWithShort:*V4]];
        [[res objectAtIndex:10] addObject:[NSNumber numberWithShort:*V5]];
        [[res objectAtIndex:11] addObject:[NSNumber numberWithShort:*V6]];
        
        p += 16;
    }
    return res;
}


#define ECG_GROUP_SIZE 16
+(NSData *)converyECGFromECGs:(NSArray <NSArray <NSNumber *>*>*)ecg_array_array swap:(short *)swapECG swapLen:(int)swapLen{
    unsigned long needLen = ecg_array_array.firstObject.count * ECG_GROUP_SIZE/2;
    if (needLen >= swapLen){
        NSLog(@"converyECGFromECGs Failed: swap space is not enought, the shorts size must more than points of number * 8");
        return nil;
    }
    
    for (int i=0;i<ecg_array_array.firstObject.count;i++){
        int j = i * 8;
        swapECG[j] = [[[ecg_array_array objectAtIndex:0] objectAtIndex:i] shortValue];
        swapECG[j+1] = [[[ecg_array_array objectAtIndex:1] objectAtIndex:i] shortValue];
        swapECG[j+2] = [[[ecg_array_array objectAtIndex:6] objectAtIndex:i] shortValue];
        swapECG[j+3] = [[[ecg_array_array objectAtIndex:7] objectAtIndex:i] shortValue];
        swapECG[j+4] = [[[ecg_array_array objectAtIndex:8] objectAtIndex:i] shortValue];
        swapECG[j+5] = [[[ecg_array_array objectAtIndex:9] objectAtIndex:i] shortValue];
        swapECG[j+6] = [[[ecg_array_array objectAtIndex:10] objectAtIndex:i] shortValue];
        swapECG[j+7] = [[[ecg_array_array objectAtIndex:11] objectAtIndex:i] shortValue];
    }
    NSData *data = [[NSData alloc] initWithBytes:(void *)swapECG length:ecg_array_array.firstObject.count*ECG_GROUP_SIZE];
    return data;
}

+(NSArray <NSArray <NSNumber *>*>* _Nullable )convertECGFromData:(void * _Nonnull)short_data withLen:(size_t)loadSize{
    if (nil == short_data){
        return nil;
    }
    if (0 != loadSize%ECG_GROUP_SIZE){
        NSLog(@"Loadsize is wrong, Can NOT convert to ECGs.");
        return nil;
    }
    
    NSMutableArray *res = [[NSMutableArray alloc] init];
    for (int i=0;i<12;i++){
        NSMutableArray *lead = [[NSMutableArray alloc] init];
        [res addObject:lead];
    }
    char *p = (char *)short_data;
    for (int i=0;i<loadSize/ECG_GROUP_SIZE;i++){
        short *I = (short *)p;
        short *II = (short *)(p+2);
        // III = II - I
        // aVR = -(I+II)/2
        // aVL = I -II/2
        // aVF = II-I/2
        short III = *II - *I;
        short aVR = - (*I + *II)/2;
        short aVL = *I - *II/2;
        short aVF = *II - *I/2;
        short *V1 = (short *)(p+4);
        short *V2 = (short *)(p+6);
        short *V3 = (short *)(p+8);
        short *V4 = (short *)(p+10);
        short *V5 = (short *)(p+12);
        short *V6 = (short *)(p+14);
        [[res objectAtIndex:0] addObject:[NSNumber numberWithShort:*I]];
        [[res objectAtIndex:1] addObject:[NSNumber numberWithShort:*II]];
        [[res objectAtIndex:2] addObject:[NSNumber numberWithShort:III]];
        [[res objectAtIndex:3] addObject:[NSNumber numberWithShort:aVR]];
        [[res objectAtIndex:4] addObject:[NSNumber numberWithShort:aVL]];
        [[res objectAtIndex:5] addObject:[NSNumber numberWithShort:aVF]];
        [[res objectAtIndex:6] addObject:[NSNumber numberWithShort:*V1]];
        [[res objectAtIndex:7] addObject:[NSNumber numberWithShort:*V2]];
        [[res objectAtIndex:8] addObject:[NSNumber numberWithShort:*V3]];
        [[res objectAtIndex:9] addObject:[NSNumber numberWithShort:*V4]];
        [[res objectAtIndex:10] addObject:[NSNumber numberWithShort:*V5]];
        [[res objectAtIndex:11] addObject:[NSNumber numberWithShort:*V6]];
        p += ECG_GROUP_SIZE;
    }
    return res;
}


#pragma mark - METs
+(float)treadmillMETsWithSpeed:(float)mph withGrade:(float)grade{
    if (mph >= 5.f){
        return 1.f + (1000.f * mph * MPHPERKMH/60.f) * (0.2f + grade * 0.009)/3.5f;
    }else{
        return 1.f + (1000.f * mph * MPHPERKMH/60.f) * (0.1f + grade * 0.018)/3.5f;
    }
}

+(float)ergometerMETsWithPower:(float)power withBodyWeight:(float)weight{
    if (0.f == weight){
        //70.f is default weight
        return 1.f + (12.f * power)/(3.5f * 70.f);
    }
    return  1.f + (12.f * power)/(3.5f * weight);
}

+(float)METwithTreanmillSpeed:(float)treanmillSpeed withtreanmillSlope:(float)treanillSlope{
    //Treadmill运动负荷计算
    return [vhECGTools treadmillMETsWithSpeed:treanmillSpeed/MPHPERKMH withGrade:treanillSlope];
}

+(float)METwithErgometerPower:(float)power withPatientBodyWeight:(float)weight{
    //Ergometer运动负荷计算
    return [vhECGTools ergometerMETsWithPower:power withBodyWeight:weight];
}

+(float)powerBySpeed:(float)speed slope:(float)slope withBodyWeight:(float)weight{
    float met = [vhECGTools METwithTreanmillSpeed:speed withtreanmillSlope:slope];
    return ((met - 1.f) * 3.5 * weight)/12.f;
}

+(int)expectedPowerWithGenderMale:(BOOL)isMale age:(int)age weight:(float)bodyWeight height:(float)bodyHeight{
    float a = 0.f;
    float b = 0.f;
    float c = 0.f;
    float d = 0.f;
    if (isMale){
        a = 6.773;
        b = 136.141;
        c = 0.064;
        d = 0.916;
    } else {
        a = 3.933;
        b = 86.641;
        c = 0.015;
        d = 0.346;
    }
    float bs = 0.007184 * pow((double)bodyWeight, (double)0.425f) * pow((double)bodyHeight, (double)0.725f);
    float power = a + (b * bs) - (c + d * bs) * (float)age;
    int powerResult = (int)power;
    if (powerResult%10 > 0){
        return 10 + 10 * (powerResult/10);
    }
    return 10 * (powerResult/10);
}
@end
